package com.linkgie.galaxyframework.binaryserialize;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.linkgie.galaxyframework.binaryserialize.impl.BinaryTypesContext;
import com.linkgie.galaxyframework.binaryserialize.impl.BinaryObjectProxy;
import com.linkgie.galaxyframework.binaryserialize.impl.HeaderEncoder;

import utils.io.BytesSlice;
import utils.io.BytesUtils;

public class BinaryProtocol {

	private final BinaryTypeRegistry REGISTRY;

	private final Object DYNAMIC_CONTRACT_TYPE_MUTEX = new Object();
	private Map<Class<?>, Class<?>> dynamicBinaryTypeMapping = new ConcurrentHashMap<>();

	public BinaryProtocol(){
		REGISTRY = BinaryTypeRegistry.create();
	}

	public BinaryTypeRegistry getRegistry(){
		return REGISTRY;
	}

	/**
	 * 返回指定的数据契约类型的映射；
	 * 
	 * @param contractType
	 * @return
	 */
	public DataTypeMapping getDataType(Class<?> contractType) {
		// TODO: Not implemented!;
		throw new IllegalStateException("Not implemented!");
	}

	/**
	 * 返回指定的数据契约类型的映射；
	 * 
	 * @param sourceClass
	 * @return
	 */
	public DataTypeMapping getDataType(int code, long version) {
		// TODO: Not implemented!;
		throw new IllegalStateException("Not implemented!");
	}

	/**
	 * 返回全部的数据契约类型的映射；
	 * 
	 * @return
	 */
	public List<DataTypeMapping> getDataTypes() {
		// TODO: Not implemented!;
		throw new IllegalStateException("Not implemented!");
	}

	/**
	 * 指定类型是否声明为 {@link BinaryType};
	 * 
	 * @param dataType
	 * @return
	 */
	public boolean isBinaryType(Class<?> dataType) {
		return BinaryTypesContext.isDataContractType(dataType);
	}

	public void write(Object data, Class<?> binaryType, OutputStream out) {
		BinaryCodec encoder = REGISTRY.register(binaryType);
		if (encoder == null) {
			throw new IllegalArgumentException("Contract Type not exist!--" + binaryType.getName());
		}
		encoder.encode(data, out);
	}

	public byte[] toBytes(Object data) {
		Class<?> dataType = data.getClass();
		Class<?> contractType = dynamicBinaryTypeMapping.get(dataType);
		if (contractType == null) {
			synchronized (DYNAMIC_CONTRACT_TYPE_MUTEX) {
				contractType = dynamicBinaryTypeMapping.get(dataType);
				if (contractType == null) {
					contractType = findBinaryType(dataType);
					if (contractType == null) {
						throw new BinaryException(
								"No data contract is declared in type[" + dataType.getName() + "]!");
					}
					dynamicBinaryTypeMapping.put(dataType, contractType);
				}
			}
		}
		return toBytes(data, contractType);
	}

	private Class<?> findBinaryType(Class<?> dataType) {
		// TODO: 未检测声明为多个 DataContract 类型的情况；
		Class<?>[] interfaces = dataType.getInterfaces();
		for (Class<?> itf : interfaces) {
			if (isBinaryType(itf)) {
				return itf;
			}
		}
		Class<?> contractType = null;
		for (Class<?> itf : interfaces) {
			contractType = findBinaryType(itf);
			if (contractType != null) {
				return contractType;
			}
		}
		return contractType;
	}

	public byte[] toBytes(Object data, Class<?> type) {
		BinaryCodec encoder = REGISTRY.register(type);
		if (encoder == null) {
			throw new IllegalArgumentException("Contract Type not exist!--" + type.getName());
		}
		return encoder.encode(data);
	}

	public <T> T read(InputStream in) {
		byte[] bytes = BytesUtils.copyToBytes(in);
		return toObject(bytes);
	}

	public <T> T toObject(byte[] dataSegment) {
		BytesSlice bytes = new BytesSlice(dataSegment, 0, dataSegment.length);
		int code = HeaderEncoder.resolveCode(bytes);
		long version = HeaderEncoder.resolveVersion(bytes);

		BinaryCodec encoder = REGISTRY.getEncoder(code, version);
		if (encoder == null) {
			throw new BinaryException(
					String.format("No data contract was registered with code[%s] and version[%s]!", code, version));
		}
		return encoder.decode(dataSegment);
	}

	public <T> T toObject(byte[] dataSegment, Class<T> type) {
		return toObject(dataSegment, type, true);
	}

	public <T> T toObject(byte[] dataSegment, Class<T> type, ObjectCreation creation) {
		return toObject(dataSegment, type, true, creation);
	}

	public <T> T toObject(byte[] dataSegment, Class<T> contractType, boolean autoRegister) {
		return toObject(dataSegment, contractType, autoRegister, null);
	}

	public <T> T toObject(byte[] dataSegment, Class<T> contractType, boolean autoRegister, ObjectCreation creation) {
		BytesSlice bytes = new BytesSlice(dataSegment, 0, dataSegment.length);
		int code = HeaderEncoder.resolveCode(bytes);
		long version = HeaderEncoder.resolveVersion(bytes);

		BinaryCodec codec = REGISTRY.getEncoder(code, version);
		if (codec == null) {
			codec = REGISTRY.getEncoder(contractType);
		} else {
			if (!contractType.isAssignableFrom(codec.getBinaryType())) {
				throw new BinaryException("The specified contract type[" + contractType.getName()
						+ "] is incompatible the origin contract type[" + codec.getBinaryType().getName()
						+ "] of the data bytes!");
			}
		}

		if (codec == null) {
			if (autoRegister) {
				codec = REGISTRY.register(contractType);
			} else {
				throw new BinaryException("Contract type is not registered! --" + contractType.toString());
			}
		}
		return codec.decode(dataSegment, creation);
	}

	public boolean isProxy(Object obj) {
		return obj instanceof BinaryObjectProxy;
	}
}
